package com.sxf.mybatis;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

/**
 * 包装的Enum类实现器
 * 
 * @author phsxf01
 * 
 * @param <E>
 */
public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

	private Class<E> type;
	private final E[] enums;

	public EnumTypeHandler(Class<E> type) {
		this.type = type;
		this.enums = type.getEnumConstants();
		if (this.enums == null) {
			throw new IllegalArgumentException(type.getSimpleName()
					+ " does not represent an enum type.");
		}
	}

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, E parameter,
			JdbcType jdbcType) throws SQLException {
		if (jdbcType == null) {
			ps.setString(i, parameter.name());
		} else {
			if (isOrdinal(jdbcType.TYPE_CODE)) {
				ps.setObject(i, parameter.ordinal(), jdbcType.TYPE_CODE);
			} else {
				ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
			}
		}

	}

	@Override
	public E getNullableResult(ResultSet rs, String columnName)
			throws SQLException {
		Object object = rs.getObject(columnName);
		return getEnum(object);
	}

	@Override
	public E getNullableResult(ResultSet rs, int columnIndex)
			throws SQLException {
		Object object = rs.getObject(columnIndex);
		return getEnum(object);
	}

	@Override
	public E getNullableResult(CallableStatement cs, int columnIndex)
			throws SQLException {
		Object object = cs.getObject(columnIndex);
		return getEnum(object);
	}

	private E getEnum(Object object) {
		if (object != null) {
			if (object instanceof Number) {
				int ordinal = ((Number) object).intValue();
				return enums[ordinal];
			} else {
				String name = (String) object;
				return Enum.valueOf(type, name);
			}
		}
		return null;
	}

	private boolean isOrdinal(int paramType) {
		switch (paramType) {
		case Types.INTEGER:
		case Types.NUMERIC:
		case Types.SMALLINT:
		case Types.TINYINT:
		case Types.BIGINT:
		case Types.DECIMAL: // for Oracle Driver
		case Types.DOUBLE: // for Oracle Driver
		case Types.FLOAT: // for Oracle Driver
			return true;
		case Types.CHAR:
		case Types.LONGVARCHAR:
		case Types.VARCHAR:
			return false;
		default:
			throw new IllegalArgumentException(
					"Unable to persist an Enum in a column of SQL Type: "
							+ paramType);
		}
	}

	/**
	 * Perform resolution of a class name.
	 * <p/>
	 * Here we first check the context classloader, if one, before delegating to
	 * {@link Class#forName(String, boolean, ClassLoader)} using the caller's
	 * classloader
	 * 
	 * @param name
	 *            The class name
	 * @param caller
	 *            The class from which this call originated (in order to access
	 *            that class's loader).
	 * @return The class reference.
	 * @throws ClassNotFoundException
	 *             From {@link Class#forName(String, boolean, ClassLoader)}.
	 */
	@SuppressWarnings("rawtypes")
	public static Class classForName(String name, Class caller)
			throws ClassNotFoundException {
		try {
			ClassLoader contextClassLoader = Thread.currentThread()
					.getContextClassLoader();
			if (contextClassLoader != null) {
				return contextClassLoader.loadClass(name);
			}
		} catch (Throwable ignore) {
		}
		return Class.forName(name, true, caller.getClassLoader());
	}

	/**
	 * Perform resolution of a class name.
	 * <p/>
	 * Same as {@link #classForName(String, Class)} except that here we delegate
	 * to {@link Class#forName(String)} if the context classloader lookup is
	 * unsuccessful.
	 * 
	 * @param name
	 *            The class name
	 * @return The class reference.
	 * @throws ClassNotFoundException
	 *             From {@link Class#forName(String)}.
	 */
	@SuppressWarnings("rawtypes")
	public static Class classForName(String name) throws ClassNotFoundException {
		try {
			ClassLoader contextClassLoader = Thread.currentThread()
					.getContextClassLoader();
			if (contextClassLoader != null) {
				return contextClassLoader.loadClass(name);
			}
		} catch (Throwable ignore) {
		}
		return Class.forName(name);
	}

}
